﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace WebApp.Common.EFQuery
{
    public static class EFExtension
    {
        public static IQueryable<TSource> Where<TSource>(this IQueryable<TSource> source,
                        QueryModel queryModel) where TSource : class
        {

            if (queryModel.Items.Count() > 0)
            {
                //构建 c=>Body中的c
                ParameterExpression param = Expression.Parameter(typeof(TSource), "c");
                //构建c=>Body中的Body
                var body = GetExpressoinBody(param, queryModel.Items);
                if (body != null)
                {
                    //将二者拼为c=>Body
                    var expression = Expression.Lambda<Func<TSource, bool>>(body, param);
                    //传到Where中当做参数，类型为Expression<Func<T,bool>>
                    return source.Where(expression);
                }
            }
            return source;
        }

        public static QueryResult FindAllByPage<TSource>(this IQueryable<TSource> source, QueryModel queryModel)
                where TSource : class
        {

            QueryResult queryResult = new QueryResult();
            queryResult.Record = new List<object>();
            queryResult.RecordCount = source.Where(queryModel).Count();
            //todo:分頁處理
            queryResult.PageNumber = queryModel.PageNumber;

            IQueryable<TSource> query = source.Where(queryModel);

            string[] orderPropertys = null;
            Type type = typeof(TSource);
            PropertyInfo property;
            bool isAsc = true;
            //沒有設置排序條件時，取第一個屬性作爲排序條件
            if (string.IsNullOrEmpty(queryModel.OrderBy))
            {
                property = type.GetRuntimeProperties().First();
                orderPropertys = new string[] { property.Name + " asc" };
            }
            else
            {
                orderPropertys = queryModel.OrderBy.Split(',');
            }
            foreach (string orderProperty in orderPropertys)
            {
                string[] orders = orderProperty.Split(' ');
                string propertyName = orders[0].Trim();
                isAsc = false;
                if (orders.Length == 2)
                {
                    if (String.Compare(orders[1].Trim(), "asc", StringComparison.OrdinalIgnoreCase) == 0) isAsc = true;
                }
                query = query.OrderBy(propertyName, isAsc);
            }

            query = query.Skip((queryModel.PageNumber - 1) * queryModel.PerPageCount).Take(queryModel.PerPageCount);
            IList<TSource> records = query.ToList();
            foreach (var item in records)
            {
                queryResult.Record.Add(item);
            }
            return queryResult;
        }

        /// <summary>构建ExpressoinBody</summary>
        private static Expression GetExpressoinBody(ParameterExpression param, IEnumerable<ConditionItem> conditions)
        {
            Expression result = null;

            //var plist = param.Type.GetRuntimeProperties().ToDictionary(z => z.Name);//可以加缓存改善性能

            //不是分组条件（条件之间是And关系）
            List<ConditionItem> notGroupConditions = conditions.Where(x => string.IsNullOrEmpty(x.OrGroup)).ToList();
            foreach (var item in notGroupConditions)
            {
                Expression e1 = GetExpressoin(param, item);
                if (e1 == null) continue;

                if (result != null) result = Expression.Add(result, e1);
                else result = e1;
            }
            //取出不同的组名（组内的条件是Or关系）
            List<string> groupNameList = conditions.Where(x => !string.IsNullOrEmpty(x.OrGroup)).Select(x => x.OrGroup).Distinct().ToList();
            Expression subExp1=null;
            foreach (string groupName in groupNameList) {
                List<ConditionItem> groupConditions = conditions.Where(x => x.OrGroup==groupName ).ToList();
                subExp1 = null;
                foreach (var item in groupConditions)
                {
                    Expression e1 = GetExpressoin(param, item);
                    if (e1 == null) continue;

                    if (subExp1 != null) subExp1 = Expression.Or(subExp1, e1);
                    else subExp1 = e1;
                }
                if (subExp1 != null) {
                    if (result != null) result = Expression.Add(result, subExp1);
                    else result = subExp1;
                }
            }
            return result;
        }

        private static Expression GetExpressoin(ParameterExpression param,ConditionItem item) {

            MemberExpression e1 = null;
            string key = item.Field;
            switch (item.Method)
            {
                case QueryMethod.Gt:
                    e1 = Expression.Property(param, key);
                    return Expression.GreaterThan(e1, Expression.Constant(item.Value));

                case QueryMethod.Ge:
                    e1 = Expression.Property(param, key);
                    return Expression.GreaterThanOrEqual(e1, Expression.Constant(item.Value));

                case QueryMethod.Lt:

                    e1 = Expression.Property(param, key);
                    return Expression.LessThan(e1, Expression.Constant(item.Value));

                case QueryMethod.Le:

                    e1 = Expression.Property(param, key);
                    return Expression.LessThanOrEqual(e1, Expression.Constant(item.Value));

                case QueryMethod.NotEqual:

                    e1 = Expression.Property(param, key);
                    return Expression.NotEqual(e1, Expression.Constant(item.Value));

                case QueryMethod.MatchFront:

                    e1 = Expression.Property(param, key);
                    return Expression.Call(e1, "StartsWith", null, Expression.Constant(item.Value));

                case QueryMethod.MatchEnd:

                    e1 = Expression.Property(param, key);
                    return Expression.Call(e1, "EndsWith", null, Expression.Constant(item.Value));

                case QueryMethod.Like:
                    e1 = Expression.Property(param, key);
                    return Expression.Call(e1, "Contains", null, Expression.Constant(item.Value));
            }

            return null;
        }

        public static IQueryable<TSource> OrderBy<TSource>(this IQueryable<TSource> source, string orderByProperty,
bool desc) where TSource : class
        {
            string command = desc ? "OrderByDescending" : "OrderBy";

            var type = typeof(TSource);
            var property = type.GetProperty(orderByProperty);
            var parameter = Expression.Parameter(type, "p");
            var propertyAccess = Expression.MakeMemberAccess(parameter, property);
            var orderByExpression = Expression.Lambda(propertyAccess, parameter);
            var resultExpression = Expression.Call(typeof(Queryable), command, new Type[] { type, property.PropertyType }, source.Expression, Expression.Quote(orderByExpression));

            return source.Provider.CreateQuery<TSource>(resultExpression);
        }
    }
}
